package player

import (
	"base"
	"config/table"
	. "game_server/internal"
	"game_server/logic/asset/api"
	"game_server/logic/db/api"
	"game_server/logic/db/event"
	"game_server/logic/db/mark"
	. "game_server/logic/internal"
	"game_server/logic/player/api"
	"game_server/logic/player/event"
	"game_server/model"
	"leaf/log"
	"proto/base"
	"proto/player"
	"proto/server/game"
	"sync"
	"time"
)

// 玩家系统
type playerSystem struct {
	players map[base.PlayerId]*model.Player
	group   *sync.WaitGroup
}

var system = new(playerSystem)

func init() {
	system.players = make(map[base.PlayerId]*model.Player)
	system.group = new(sync.WaitGroup)
}

func init() {
	player_api.Get = system.Get
	player_api.Login = system.Login
	player_api.Logout = system.Logout
}

func init() {
	Skeleton.BeforeExit(system.onExit)
}

func (*playerSystem) Login(playerId base.PlayerId, loginTimestamp int64, gateId base.AgentId) {
	base.Logger.Release("玩家: %v 登录游戏", playerId)

	response := new(proto_server_game.OnLogin)
	response.PlayerId = playerId.String()

	player, exists := system.Get(playerId)
	isFirst := !exists
	if exists {
		player.Call(func() {
			system.onLogin(player, loginTimestamp, isFirst)
		})
		rpc.OnLogin(response)
	} else {
		logger, _ := log.New(
			Config.Player.LogLevel,
			Config.Player.LogPath+playerId.String(),
			Config.Player.LogFlat,
		)

		player = model.NewPlayer(playerId, gateId, logger)

		var loadSuccess bool
		load := func() {
			loadSuccess = db_api.Load(player)
		}

		onLoaded := func() {
			if loadSuccess {
				system.add(player)
				system.group.Add(1)
				go player.Run(system.group)
				player.Call(func() {
					system.onLogin(player, loginTimestamp, isFirst)
				})
			} else {
				response.Code = proto_base.ErrorCode_LoadPlayerFailed
			}

			rpc.OnLogin(response)
		}

		Skeleton.Go(load, onLoaded)
	}
}

func (*playerSystem) onLogin(player *model.Player, loginTimestamp int64, isFirst bool) {
	player.SetOnline(true)
	player.SetLoginTimestamp(loginTimestamp)
	if player.ExitTimer != nil {
		player.StopTimer(player.ExitTimer)
		player.ExitTimer = nil
	}

	detail := player.Detail
	if detail.Level < 1 {
		detail.Level = 1
	}

	if detail.Name == "" {
		detail.Name = "未命名"
	}

	detail.LastLoginTime = loginTimestamp
	if !detail.InitReward {
		callback := func() {
			detail.InitReward = true
		}

		asset_api.Handle(
			player,
			config_table.Global.InitReward,
			nil,
			proto_base.ChangeReason_InitReward,
			callback,
		)
	}
	Event.Notify(db_event.Collect, player, db_mark.Detail)

	Event.Notify(player_event.OnLogin, player, isFirst)
}

func (*playerSystem) Logout(playerId base.PlayerId) {
	base.Logger.Debug("player on logout, playerId: %v", playerId)
	player, exists := system.Get(playerId)
	if !exists {
		return
	}

	player.Call(func() {
		system.onLogout(player)
	})
}

func (*playerSystem) onLogout(player *model.Player) {
	if !player.Online() {
		return
	}

	player.SetOnline(false)
	if player.ExitTimer != nil {
		const delay = 300 * time.Second
		player.ExitTimer = player.AfterCall(delay, func() {
			player.ExitTimer = nil
			rpc.OnLogout(player, proto_base.LogoutReason_Exit)

			system.remove(player)
		})
	}

	Event.Notify(player_event.OnLogout, player)
}

func (*playerSystem) onExit() {
	for _, player := range system.players {
		system.remove(player)
	}

	system.group.Wait()
}

func (*playerSystem) Get(playerId base.PlayerId) (result *model.Player, exists bool) {
	result, exists = system.players[playerId]
	return
}

func (*playerSystem) add(player *model.Player) {
	system.players[player.Id()] = player
}

func (*playerSystem) remove(player *model.Player) {
	Event.Notify(player_event.OnExit, player)

	// save player to db
	if dirtyMark := player.ResetDirtyMark(); dirtyMark.Any() {
		db_api.Save(player, dirtyMark)
	}

	delete(system.players, player.Id())
	player.ExitSignal <- true
}

func (*playerSystem) Has(playerId base.PlayerId) (exists bool) {
	_, exists = system.players[playerId]
	return
}

func (*playerSystem) Count() int {
	return len(system.players)
}

func (*playerSystem) SetLevel(player *model.Player, newLevel uint16) {
	curLevel := player.Detail.Level
	if curLevel == newLevel {
		return
	}

	player.Detail.Level = newLevel
	Event.Notify(db_event.Collect, player, db_mark.Detail)
	Event.Notify(player_event.OnLevelChange, player, newLevel, curLevel)

	msg := new(proto_player.SyncLevel)
	msg.New = uint32(newLevel)
	msg.Old = uint32(curLevel)
	rpc.SyncLevel(player, msg)
}

func (*playerSystem) setName(player *model.Player, newName string) {
	if newName == "" {
		return
	}

	curName := player.Detail.Name
	if curName == newName {
		return
	}

	player.Detail.Name = newName
	Event.Notify(db_event.Collect, player, db_mark.Detail)
}
